package stockGP;

import genetic_programming.Chromosome;
import genetic_programming.GP;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Random;

import main.Stock;

public class StockGP implements GP{


	// the population size
	int POPULATION_SIZE=6;
	// the percentage for performing a mutation an a child chromosome
	int MUTATION_RATE=200;
	// the number of generations
	int NUM_OF_GENERATIONS=5000;
	// the percentage of performing crossover between two parents 
	int CROSSOVER_RATE=95;
	int DEPTH=5;
	public enum Constraint {
	    ALL_BOOLEAN_OPERATOR,MATH_OPERATOR
	}

	int DAYS;
	@Override
	public ArrayList<Chromosome> initiate_population(int size) {
		
		ArrayList<Chromosome> population=new ArrayList<Chromosome>(size);
		int index;

		Tree t;
		// initialize every chromosome in the first population
		for(int i=0;i<size;i++)
		{
			t = initTree(0);
			population.add(new StockChromosome(t));
		}

		return population;
		
	
	}
	
	private Tree initTree(int depth) {
		TreeNode tn;
		Tree.count=0;
		tn = initTreeNode(depth, Constraint.ALL_BOOLEAN_OPERATOR);
		Tree t = new Tree(tn, Tree.count);
		Tree.count=0;
		return t;
	}
	
	private TreeNode initTreeNode(int depth,Constraint key) {
		Random r=new Random();
		int num;
		boolean isLeaf = false;
		String operation="";
		if(depth==DEPTH-1)
		{
			num=r.nextInt(3);
			if(num==0){
				int index=r.nextInt(DAYS);
				operation="x"+index;
			}
			if(num==1){
				int index=r.nextInt(1000);
				operation=String.valueOf(index);
			}
			if(num==2){
				operation="mv";
			}
			
			return new TreeNode(new ArrayList<TreeNode>(), operation,-1);
			
		}
		
		
		
		ArrayList<TreeNode> children = new ArrayList<TreeNode>();
		TreeNode tn = null;
		if(key==Constraint.ALL_BOOLEAN_OPERATOR){
			num=r.nextInt(5);
			//0:and, 1:or, 2:<, 3:>, 4:=
			if(num==0){
				operation="and";
				children.add(initTreeNode(depth+1,Constraint.ALL_BOOLEAN_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.ALL_BOOLEAN_OPERATOR));
			}
			if(num==1){
				operation="or";
				children.add(initTreeNode(depth+1,Constraint.ALL_BOOLEAN_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.ALL_BOOLEAN_OPERATOR));
			}
			if(num==2){
				operation=">";
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
			}
			if(num==3){
				operation="<";
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
			}
			if(num==4){
				operation="=";
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
			}
			isLeaf = false;
		}
		else if(key==Constraint.MATH_OPERATOR){
			
			num=r.nextInt(7);
			//0:+,1:-,2:*,3:/,4:var,5:cons,6:mov_avg
			if(num==0){
				operation="+";
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
			}
			if(num==1){
				operation="-";
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
			}
			if(num==2){
				operation="*";
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
			}
			if(num==3){
				operation="/";
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
				children.add(initTreeNode(depth+1,Constraint.MATH_OPERATOR));
			}
			if(num==4){
				int index=r.nextInt(DAYS);
				operation="x"+index;
				isLeaf = true;
			}
			if(num==5){
				int index=r.nextInt(1000);
				operation=String.valueOf(index);
				isLeaf = true;
			}
			if(num==6){
				operation="mv";
				isLeaf = true;
			}
		}
		
		if(isLeaf){
			tn= new TreeNode(children, operation,-1);
		}
		else{
			tn= new TreeNode(children, operation,Tree.count++);
		}
		return tn;
	}

	@Override
	public ArrayList<Chromosome> selection(ArrayList<Chromosome> population,
			double avg) {		

		int N =population.size();
		//int T = calcSumExpVal(population,avg);
		int T=N;

		Random r=new Random();
		// get a random number
		double num=r.nextInt(T);
		double sum = 0.0;
		int j;

		ArrayList<Chromosome> selected=new ArrayList<Chromosome>();

		// choose 2N+2 parents
		for(int i=0;i<((2*N)+2);i++)
		{
			// for each chromosome in the population
			for(j=0;j<N;j++)
			{
				// add the exp value to the sum
				sum+=ExpVal(j,population,avg);
				// the case that this "j" chromosome was selected
				if(sum>num)
				{	
					selected.add(population.get(j));
					// change in fixed distance
					num = (num+((double)T/N)) % T;
					sum = 0.0;
					break;
				}
			} 
		}
		
		return selected;
		
	}

	@Override
	public ArrayList<Chromosome> crossover(Chromosome parent1,
			Chromosome parent2) {
		Random r=new Random();

		ArrayList<Chromosome> children=new ArrayList<Chromosome>();

		Tree child = null;
		int num;

		// get a random number (between 0 and 100)
		num=r.nextInt(100);

		// the case that this time the two parents will continue to the next generation with no crossover
		if(num>=CROSSOVER_RATE)
		{
			num=r.nextInt(2);
			if(num==0)
				children.add(parent1);
			else
				children.add(parent2);
			return children;
		}

		child=((Tree)(parent1.getData())).crossover(new Tree((Tree)parent2.getData()));
		// add the children to the result array (with possible mutation)
		children.add(mutation(new StockChromosome(child)));
	
		return children;
	}

	@Override
	public Chromosome mutation(Chromosome chrm) {
		System.out.println("m");
		Tree t = (Tree) chrm.getData();
		t.root.mutation(MUTATION_RATE);
		return new StockChromosome(new Tree(new TreeNode(t.root), t.interLength));
	}

	public void run_alg(ArrayList<Stock> stocks,int days) throws IOException
	{
		/*
		DAYS=4;
		TreeNode.days=4;
		StockChromosome.days=4;
		Tree t=(Tree) ((initiate_population(1).get(0)).getData());
		//Chromosome chrm = initiate_population(1).get(0);
		//Tree t1 = (Tree)(chrm.getData());
		//t1.printTree();
		//Tree t = (Tree)(mutation(chrm).getData());
		//System.out.println("and the second tree");
		//t.printTree();
		Tree t2=(Tree) ((initiate_population(1).get(0)).getData());
	
		(t.crossover(t2)).printTree();
		
		if(true)
		return;
		*/
		
		// Initialize variables
		double avg;
		DAYS=days;
		StockChromosome.days=days;
		TreeNode.days=days;
		int gen = 100;
		
		/*
		// objects for writing the needed data for the learning graphs
		FileWriter fw=new FileWriter("tsp_graph2.csv");
		fw.write("num_generation,best_fitness,avg_fitness");
		fw.write('\n');*/

		Random r=new Random();

		// initialize the population
		ArrayList<Chromosome> population=initiate_population(POPULATION_SIZE);


		int count=0;
		ArrayList<Chromosome> selected;
		ArrayList<Chromosome> children;

		// for each generation
		while(count<NUM_OF_GENERATIONS)
		{
			Chromosome best=getMaxAndRemoveWorst(population);

			// the case that 'gen' number of iterations have passed
			if(count == gen)
			{
				System.out.println("gen: " + count + " and best fit so far: " + best.evaluateFitness());
				gen+=100;
			}
			
			//fw.write(count+","+best.evaluateFitness());


			population.remove(best);
			Chromosome second_best=getMaxAndRemoveWorst(population);
			population.add(best);

			// calculating the average fitness
			avg=calcAvgFitness(population);

			//fw.write(","+avg);
			//fw.write('\n');

			// selecting the parents for crossover
			selected=selection(population,avg);
			population.clear();
			
			// initiating the not picked array to all the chromosomes
			ArrayList<Integer> indexes_not_chosen_yet=new ArrayList<Integer>();
			for(int i=0;i<POPULATION_SIZE-2;i++)
				indexes_not_chosen_yet.add(i);
			
			int index1,index2;
			// for every random two parents from the selection array
			for(int i=0;i<POPULATION_SIZE-2;i+=2)
			{
				index1=r.nextInt(indexes_not_chosen_yet.size());
				int first_index=indexes_not_chosen_yet.get(index1);
				indexes_not_chosen_yet.remove(index1);
				index2=r.nextInt(indexes_not_chosen_yet.size());
				int second_index=indexes_not_chosen_yet.get(index2);
				indexes_not_chosen_yet.remove(index2);

				// performing crossover between two parents
				children=crossover(selected.get(second_index),selected.get(first_index));
				// adding those new children to the next population
				population.addAll(children);
			}

			// replacing the two best chromosome from this generation to the next one
			population.add(best);
			population.add(second_best);
			
			count++;
		}

		// getting the best route the GA has found
		Chromosome final_solution=getBestSolution(population);
		
		System.out.println("our GA price "+ final_solution.evaluateFitness());
		//System.out.println("best price "+ right_solution_fitness);
		//System.out.println("difference "+( right_solution_fitness- final_solution.evaluateFitness()));

		//fw.close();
	}

	/*
	 * this function returns the chromosome with the best fitness in the population
	 * and removes the worst one 
	 * @param population - the current population
	 * @param right - the best fitness possiable
	 * @return - the best chromosome
	 */
	private Chromosome getMaxAndRemoveWorst(ArrayList<Chromosome> population)
	{
		int max_fitness=-1;
		int max_index=-1;

		int min_fitness = 100000;//!!!!!!!!!!!!!!!!!!!!!!fix 
		int min_index=-1;

		// for every chromosome in the population 
		for(int i=0;i<population.size();i++)
		{
			// the case that this chromosome's fitness is bigger then the current maximal
			if(population.get(i).evaluateFitness()>max_fitness)
			{
				// update the maximum fitness
				max_fitness=(int) population.get(i).evaluateFitness();
				max_index=i;
			}

			// the case that this chromosome's fitness is lower then the current minimal
			if(population.get(i).evaluateFitness()<min_fitness)
			{
				// update the minimum fitness
				min_fitness=(int) population.get(i).evaluateFitness();
				min_index=i;
			}
		}

		Chromosome best;

		// the case that the whole population has the same fitness
		if(max_index == min_index)
		{
			best=population.get(1);
			// remove the worst
			population.remove(0);
		}
		// the case that the whole population doesn't have the same fitness
		else
		{
			best=population.get(max_index);
			// remove the worst
			population.remove(min_index);
		}
		return best;
	}


	/*
	 * this function return the chromosome with the best fitness in the population
	 * @param population - the current population
	 * @return - the best chromosome in the population
	 */
	private Chromosome getBestSolution(ArrayList<Chromosome> population)
	{
		int max_fitness=-2000000;
		int max_index=-1;

		// for every chromosome in the population 
		for(int i=0;i<population.size();i++)
		{
			// the case that this chromosome's fitness is bigger then the current maximal
			if(population.get(i).evaluateFitness()>max_fitness)
			{
				// update the maxium fitness
				max_fitness=(int) population.get(i).evaluateFitness();
				max_index=i;
			}

		}

		Chromosome best=population.get(max_index);
		return best;
	}
	
	/*
	private double CalcStandardDevitation(ArrayList<Chromosome> population, double avg)
	{
		double sum=0;
		for(Chromosome chrm:population)
		{
			sum+=Math.pow(chrm.evaluateFitness()-avg, 2);
		}
		return Math.sqrt(sum/population.size());
	}
	*/
	
	/*
	 * this function calculates the average fitness of the given population
	 * @param population - the current population
	 * @return - the average fitness of this population
	 */
	private double calcAvgFitness(ArrayList<Chromosome> population){

		double sum=0;

		// for every chromosome in the population
		for(Chromosome chrm:population)
		{
			// add this chromosome fitness
			sum+=chrm.evaluateFitness();
		}

		// return the average fitness
		return ((double)sum)/population.size();
	}

	private double ExpVal(int i,ArrayList<Chromosome> population,double avg)
	{
		/*	 
		double sdev=CalcStandardDevitation(population, avg);
		double temp;
		if(sdev!=0)
		{
			temp = 1+((population.get(i).evaluateFitness()-avg)/2*sdev);
			if(temp >= 0)
			{
				return temp;
			}
			else
			{
				return 0.00001;
			}
		}
		else
			return 1;
		*/ 

		return (population.get(i).evaluateFitness())/avg;
	}
}
